#include <iostream>
#include <fstream>
#include <assert.h>
#include "GLShader.h"
#include <GLUT/glut.h>

void ReportError(const char* buf)
{
  std::cout << buf << "\n";
}

GLShader::GLShader()
{
  m_program = 0;
}

void GLShader::Set(const std::string& name, float f)
{
    GLint loc = glGetUniformLocation(m_program, name.c_str());
    if (loc == -1)
    {
        return;
    }

    glUniform1f(loc, f);
}

void GLShader::Set(const std::string& name, int i)
{
    GLint loc = glGetUniformLocation(m_program, name.c_str());
    if (loc == -1)
    {
        return;
    }

    glUniform1f(loc, i);
}

bool ReadFile(const char* filename, std::string* result)
{
  std::ifstream f;
  f.open(filename);
  if (!f.is_open())
  {
    std::cout << "Failed to open file " << filename << "\n";
    return false;
  }

  while (!f.eof())
  {
    std::string str;
    std::getline(f, str);
    (*result) += str + "\r\n";
  }

  return true;
}

bool GLShader::Load(const char* vertFilename, const char* fragFilename)
{
  std::string v;
  if (!ReadFile(vertFilename, &v))
  {
    std::cout << "Failed to read vertex shader from file\n";
    return false;
  }

  std::string f;
  if (!ReadFile(fragFilename, &f))
  {
    std::cout << "Failed to read fragment shader from file\n";
    return false;
  }
 
  return Create(v, f); 
}

bool GLShader::Create(const std::string& vertexSource, const std::string& fragmentSource)
{
  GLuint vertSh = glCreateShader(GL_VERTEX_SHADER);
  GLuint fragSh = glCreateShader(GL_FRAGMENT_SHADER);

  const GLint vlength = vertexSource.size();
  const GLint flength = fragmentSource.size();
  const char* vStr = vertexSource.c_str();
  const char* fStr = fragmentSource.c_str();
  glShaderSource(vertSh, 1, &vStr, &vlength);
  glShaderSource(fragSh, 1, &fStr, &flength);

  GLint compiled = 0;
  static const int ERROR_BUF_SIZE = 2000;
  GLcharARB buf[ERROR_BUF_SIZE]; // error string buffer

  glCompileShader(vertSh);

  glGetShaderiv(vertSh, GL_COMPILE_STATUS, &compiled);
  if (!compiled)
  {
    glGetShaderInfoLog(vertSh, ERROR_BUF_SIZE, 0, buf);
    ReportError(buf);
    return false;
  }

  glCompileShader(fragSh);
  
  glGetShaderiv(fragSh, GL_COMPILE_STATUS, &compiled);
  if (!compiled)
  {
    glGetShaderInfoLog(fragSh, ERROR_BUF_SIZE, 0, buf);
    ReportError(buf);
    return false;
  }

  m_program = glCreateProgram();
  glAttachShader(m_program, vertSh);
  glAttachShader(m_program, fragSh);
  glLinkProgram(m_program);
  GLint linked;
  glGetProgramiv(m_program, GL_OBJECT_LINK_STATUS_ARB, &linked);
  if (!linked)
  {
    glGetProgramInfoLog(m_program, ERROR_BUF_SIZE, 0, buf);
    ReportError(buf);
    return false;
  }

  UseThisShader(); // bind shader so we can set uniforms

  return true;
}

void GLShader::UseThisShader()
{
  glUseProgram(m_program);
}

